//
//  Currency.swift
//  MultiCurrencyMoney
//
//  Created by lanjing on 2021/3/29.
//

import Foundation
typealias CurrencyValue = Float
typealias CurrencyRate = Float

protocol Expression {
    func plus(_ addend: Expression) -> Expression
    func times(_ multiplier: Float) -> Expression
    func reduced(in bank: Bank, to currency: String) -> Money
}

class Bank{
    private struct Pair: Equatable, Hashable{
        static func == (lhs: Bank.Pair, rhs: Bank.Pair) -> Bool {
            return lhs.from == rhs.from && lhs.to == rhs.to
        }
        
        let from: String
        let to: String

        func reversed() -> Pair{
            return Pair(from: to, to: from)
        }
    }
    
    private var rateMap = [Pair : CurrencyRate]()
    
    func rate(from: String, to: String) -> CurrencyRate{
        if from == to{
            return 1.0
        }
        
        let pair = Pair(from: from, to: to)
        if let rate = rateMap[pair]{
            return rate
        }
        //FIXME:需要处理找不到汇率的情况
        let rate = rateMap[pair.reversed()]!
        return 1.0/rate
    }
    
    func addRate(from: String, to: String, rate: CurrencyRate){
        let pair = Pair(from: from, to: to)
        rateMap[pair] = rate
    }
    
    func reduce(source: Expression, to currency: String) -> Money{
        return source.reduced(in: self, to: currency)
    }
}

struct Sum: Expression{
    let augend: Expression
    let addend: Expression
    
    func plus(_ addend: Expression) -> Expression {
        return Sum(augend: self, addend: addend)
    }
    
    func times(_ multiplier: Float) -> Expression {
        return Sum(augend: augend.times(multiplier), addend: addend.times(multiplier))
    }
    
    func reduced(in bank: Bank, to currency: String) -> Money {
        let money1 = augend.reduced(in: bank, to: currency)
        let money2 = addend.reduced(in: bank, to: currency)
        return Money(amount: money1.amount + money2.amount, currency: currency)
    }
}

struct Money{
    static func dollar(amount: CurrencyValue) -> Money{
        return Money(amount: amount, currency: "USD")
    }
    
    static func franc(amount: CurrencyValue) -> Money{
        return Money(amount: amount, currency: "CHF")
    }
    
    internal let amount: CurrencyValue
    internal var currency: String
    init(amount: CurrencyValue, currency: String) {
        self.amount = amount
        self.currency = currency
    }
   
    var description: String {
        get {
            return String(amount) + " " + currency
        }
    }
}

extension Money: Equatable{
    static func == (lhs: Money, rhs: Money) -> Bool {
        return lhs.amount == rhs.amount && lhs.currency == rhs.currency
    }
}
extension Money: Expression{
    func plus(_ addend: Expression) -> Expression {
        return Sum(augend: self, addend: addend)
    }
    
    func times(_ multiplier: Float) -> Expression{
        return Money(amount: amount * multiplier, currency:self.currency)
    }
    
    func reduced(in bank: Bank, to currency: String) -> Money {
        let rate = bank.rate(from: self.currency, to: currency)
        
        return Money(amount: amount/rate, currency: currency)
    }
}
